/*
 *  Checkpoint/restart - dump state of sysvipc msg
 *
 *  Copyright (C) 2009 Oren Laadan
 *
 *  This file is subject to the terms and conditions of the GNU General Public
 *  License.  See the file COPYING in the main directory of the Linux
 *  distribution for more details.
 */

/* default debug level for output */
#define CKPT_DFLAG  CKPT_DIPC

#include <linux/mm.h>
#include <linux/msg.h>
#include <linux/rwsem.h>
#include <linux/sched.h>
#include <linux/syscalls.h>
#include <linux/nsproxy.h>
#include <linux/security.h>
#include <linux/ipc_namespace.h>

#include "util.h"

#include <linux/checkpoint.h>

/************************************************************************
 * ipc checkpoint
 */

/* called with the msgids->rw_mutex is read-held */
static int fill_ipc_msg_hdr(struct ckpt_ctx *ctx,
			    struct ckpt_hdr_ipc_msg *h,
			    struct msg_queue *msq)
{
	int ret;

	ret = checkpoint_fill_ipc_perms(ctx, &h->perms, &msq->q_perm);
	if (ret < 0)
		return ret;

	ipc_lock_by_ptr(&msq->q_perm);
	h->q_stime = msq->q_stime;
	h->q_rtime = msq->q_rtime;
	h->q_ctime = msq->q_ctime;
	h->q_cbytes = msq->q_cbytes;
	h->q_qnum = msq->q_qnum;
	h->q_qbytes = msq->q_qbytes;
	h->q_lspid = msq->q_lspid;
	h->q_lrpid = msq->q_lrpid;
	ipc_unlock(&msq->q_perm);

	ckpt_debug("msg: lspid %d rspid %d qnum %lld qbytes %lld\n",
		 h->q_lspid, h->q_lrpid, h->q_qnum, h->q_qbytes);

	return 0;
}

static int checkpoint_msg_contents(struct ckpt_ctx *ctx, struct msg_msg *msg)
{
	struct ckpt_hdr_ipc_msg_msg *h;
	struct msg_msgseg *seg;
	int total, len;
	int secref, ret;

	secref = security_checkpoint_obj(ctx, msg->security,
				      CKPT_SECURITY_MSG_MSG);
	if (secref < 0) {
		ckpt_err(ctx, secref, "%(T)msg_msg->security");
		return secref;
	}
	h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_IPC_MSG_MSG);
	if (!h)
		return -ENOMEM;

	h->m_type = msg->m_type;
	h->m_ts = msg->m_ts;
	h->sec_ref = secref;

	ret = ckpt_write_obj(ctx, &h->h);
	ckpt_hdr_put(ctx, h);
	if (ret < 0)
		return ret;

	total = msg->m_ts;
	len = min(total, (int) DATALEN_MSG);
	ret = ckpt_write_buffer(ctx, (msg + 1), len);
	if (ret < 0)
		return ret;

	seg = msg->next;
	total -= len;

	while (total) {
		len = min(total, (int) DATALEN_SEG);
		ret = ckpt_write_buffer(ctx, (seg + 1), len);
		if (ret < 0)
			break;
		seg = seg->next;
		total -= len;
	}

	return ret;
}

static int checkpoint_msg_queue(struct ckpt_ctx *ctx, struct msg_queue *msq)
{
	struct list_head messages;
	struct msg_msg *msg;
	int ret = -EBUSY;

	/*
	 * Scanning the msq requires the lock, but then we can't write
	 * data out from inside. Instead, we grab the lock, remove all
	 * messages to our own list, drop the lock, write the messages,
	 * and finally re-attach the them to the msq with the lock taken.
	 */
	ipc_lock_by_ptr(&msq->q_perm);
	if (!list_empty(&msq->q_receivers))
		goto unlock;
	if (!list_empty(&msq->q_senders))
		goto unlock;
	if (list_empty(&msq->q_messages))
		goto unlock;
	/* temporarily take out all messages */
	INIT_LIST_HEAD(&messages);
	list_splice_init(&msq->q_messages, &messages);
 unlock:
	ipc_unlock(&msq->q_perm);

	list_for_each_entry(msg, &messages, m_list) {
		ret = checkpoint_msg_contents(ctx, msg);
		if (ret < 0)
			break;
	}

	/* put all the messages back in */
	ipc_lock_by_ptr(&msq->q_perm);
	list_splice(&messages, &msq->q_messages);
	ipc_unlock(&msq->q_perm);

	return ret;
}

/* called with the msgids->rw_mutex is read-held */
int checkpoint_ipc_msg(int id, void *p, void *data)
{
	struct ckpt_hdr_ipc_msg *h;
	struct ckpt_ctx *ctx = (struct ckpt_ctx *) data;
	struct kern_ipc_perm *perm = (struct kern_ipc_perm *) p;
	struct msg_queue *msq;
	int ret;

	msq = container_of(perm, struct msg_queue, q_perm);

	h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_IPC_MSG);
	if (!h)
		return -ENOMEM;

	ret = fill_ipc_msg_hdr(ctx, h, msq);
	if (ret < 0)
		goto out;

	ret = ckpt_write_obj(ctx, &h->h);
	if (ret < 0)
		goto out;

	if (h->q_qnum)
		ret = checkpoint_msg_queue(ctx, msq);
 out:
	ckpt_hdr_put(ctx, h);
	return ret;
}


/************************************************************************
 * ipc restart
 */

/* called with the msgids->rw_mutex is write-held */
static int load_ipc_msg_hdr(struct ckpt_ctx *ctx,
			    struct ckpt_hdr_ipc_msg *h,
			    struct msg_queue *msq)
{
	int ret = 0;

	ret = restore_load_ipc_perms(ctx, &h->perms, &msq->q_perm);
	if (ret < 0)
		return ret;

	ckpt_debug("msq: lspid %d lrpid %d qnum %lld qbytes %lld\n",
		 h->q_lspid, h->q_lrpid, h->q_qnum, h->q_qbytes);

	if (h->q_lspid < 0 || h->q_lrpid < 0)
		return -EINVAL;

	msq->q_stime = h->q_stime;
	msq->q_rtime = h->q_rtime;
	msq->q_ctime = h->q_ctime;
	msq->q_lspid = h->q_lspid;
	msq->q_lrpid = h->q_lrpid;

	return 0;
}

static struct msg_msg *restore_msg_contents_one(struct ckpt_ctx *ctx, int *clen)
{
	struct ckpt_hdr_ipc_msg_msg *h;
	struct msg_msg *msg = NULL;
	struct msg_msgseg *seg, **pseg;
	int total, len;
	int ret;

	h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_IPC_MSG_MSG);
	if (IS_ERR(h))
		return (struct msg_msg *) h;

	ret = -EINVAL;
	if (h->m_type < 1)
		goto out;
	if (h->m_ts > current->nsproxy->ipc_ns->msg_ctlmax)
		goto out;

	total = h->m_ts;
	len = min(total, (int) DATALEN_MSG);
	msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL);
	if (!msg) {
		ret = -ENOMEM;
		goto out;
	}
	msg->next = NULL;
	pseg = &msg->next;

	/* set default MAC attributes */
	ret = security_msg_msg_alloc(msg);
	if (ret < 0)
		goto out;

	/* if requested and allowed, reset checkpointed MAC attributes */
	ret = security_restore_obj(ctx, (void *) msg, CKPT_SECURITY_MSG_MSG,
				   h->sec_ref);
	if (ret < 0)
		goto out;

	ret = _ckpt_read_buffer(ctx, (msg + 1), len);
	if (ret < 0)
		goto out;

	total -= len;
	while (total) {
		len = min(total, (int) DATALEN_SEG);
		seg = kmalloc(sizeof(*seg) + len, GFP_KERNEL);
		if (!seg) {
			ret = -ENOMEM;
			goto out;
		}
		seg->next = NULL;
		*pseg = seg;
		pseg = &seg->next;

		ret = _ckpt_read_buffer(ctx, (seg + 1), len);
		if (ret < 0)
			goto out;
		total -= len;
	}

	msg->m_type = h->m_type;
	msg->m_ts = h->m_ts;
	*clen = h->m_ts;
 out:
	if (ret < 0 && msg) {
		free_msg(msg);
		msg = ERR_PTR(ret);
	}
	ckpt_hdr_put(ctx, h);
	return msg;
}

static inline void free_msg_list(struct list_head *queue)
{
	struct msg_msg *msg, *tmp;

	list_for_each_entry_safe(msg, tmp, queue, m_list)
		free_msg(msg);
}

static int restore_msg_contents(struct ckpt_ctx *ctx, struct list_head *queue,
				unsigned long qnum, unsigned long *cbytes)
{
	struct msg_msg *msg;
	int clen = 0;
	int ret = 0;

	INIT_LIST_HEAD(queue);

	*cbytes = 0;
	while (qnum--) {
		msg = restore_msg_contents_one(ctx, &clen);
		if (IS_ERR(msg))
			goto fail;
		list_add_tail(&msg->m_list, queue);
		*cbytes += clen;
	}
	return 0;
 fail:
	ret = PTR_ERR(msg);
	free_msg_list(queue);
	return ret;
}

int restore_ipc_msg(struct ckpt_ctx *ctx, struct ipc_namespace *ns)
{
	struct ckpt_hdr_ipc_msg *h;
	struct kern_ipc_perm *ipc;
	struct msg_queue *msq;
	struct ipc_ids *msg_ids = &ns->ids[IPC_MSG_IDS];
	struct list_head messages;
	unsigned long cbytes;
	int msgflag;
	int ret;

	INIT_LIST_HEAD(&messages);

	h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_IPC_MSG);
	if (IS_ERR(h))
		return PTR_ERR(h);

	ret = -EINVAL;
	if (h->perms.id < 0)
		goto out;

	/* read queued messages into temporary queue */
	ret = restore_msg_contents(ctx, &messages, h->q_qnum, &cbytes);
	if (ret < 0)
		goto out;

	ret = -EINVAL;
	if (h->q_cbytes != cbytes)
		goto out;

	/* restore the message queue */
	msgflag = h->perms.mode | IPC_CREAT | IPC_EXCL;
	ckpt_debug("msg: do_msgget key %d flag %#x id %d\n",
		 h->perms.key, msgflag, h->perms.id);
	ret = do_msgget(ns, h->perms.key, msgflag, h->perms.id);
	ckpt_debug("msg: do_msgget ret %d\n", ret);
	if (ret < 0)
		goto out;

	down_write(&msg_ids->rw_mutex);

	/*
	 * We are the sole owners/users of this brand new ipc-ns, so:
	 *
	 * 1) The msgid could not have been deleted between its creation
	 *   and taking the rw_mutex above.
	 *
	 * 2) No unauthorized task will have attempted to gain access
	 *   to it either, not even until we restore the security bit
	 *   further below, so the theoretical security race is void.
	 *
	 * If/when we allow to restore the ipc state within the parent's
	 * ipc-ns, we will need to re-examine this.
	 */
	ipc = ipc_lock(msg_ids, h->perms.id);
	BUG_ON(IS_ERR(ipc));

	msq = container_of(ipc, struct msg_queue, q_perm);
	BUG_ON(!list_empty(&msq->q_messages));

	/* attach queued messages we read before */
	list_splice_init(&messages, &msq->q_messages);

	/* adjust msq and namespace statistics */
	atomic_add(h->q_cbytes, &ns->msg_bytes);
	atomic_add(h->q_qnum, &ns->msg_hdrs);
	msq->q_cbytes = h->q_cbytes;
	msq->q_qbytes = h->q_qbytes;
	msq->q_qnum = h->q_qnum;

	/* this is safe because no unauthorized access is possible */
	ipc_unlock(ipc);

	ret = load_ipc_msg_hdr(ctx, h, msq);
	if (ret < 0) {
		ckpt_debug("msq: need to remove (%d)\n", ret);
		ipc_lock_by_ptr(&msq->q_perm);
		freeque(ns, ipc);
		ipc_unlock(ipc);
	}
	up_write(&msg_ids->rw_mutex);
 out:
	free_msg_list(&messages);  /* no-op if all ok, else cleanup msgs */
	ckpt_hdr_put(ctx, h);
	return ret;
}
